package checker

import (
	"mi/compiler/ast"
	"mi/compiler/binder"
)

func (c *checker) instantiateTypeParameter(typeParameter *typeParameter, mapper *TypeMapper) *typeParameter {
	var result = c.newTypeParam(TFTypeParameter, typeParameter.Symbol())
	if typeParameter.constraint != nil {
		result.constraint = c.instantiateType(typeParameter.constraint, mapper)
	} else {
		result.target = typeParameter
		result.mapper = mapper
	}
	return result
}

func (c *checker) instantiateSignature(signature *Signature, mapper *TypeMapper, eraseTypeParameters bool) *Signature {
	var freshTypeParameters []*typeParameter
	if !isNull(signature.TypeParameters) && !eraseTypeParameters {
		freshTypeParameters = c.instantiateTypeParameterList(signature.TypeParameters, mapper, c.instantiateTypeParameter)
		mapper = c.combineTypeMappers(c.createTypeMapper(toTypes(signature.TypeParameters), toTypes(freshTypeParameters)), mapper)
	}

	var throwTypes []Type
	if !isNull(signature.Throws) {
		for _, throw := range signature.Throws {
			throwTypes = append(throwTypes, c.instantiateType(throw, mapper))
		}
	}

	var resolvedReturnType Type
	if !isNull(signature.ResolvedReturnType) {
		resolvedReturnType = c.instantiateType(signature.ResolvedReturnType, mapper)
	}

	var result = c.createSignature(signature.Declaration, freshTypeParameters,
		c.instantiateSymbolList(signature.Parameters, mapper, c.instantiateSymbol), throwTypes,
		resolvedReturnType, signature.MinArgumentCount, signature.HasRestParameter)
	result.Target = signature
	result.Mapper = mapper
	return result
}

func (c *checker) instantiateSymbol(symbol *binder.Symbol, mapper *TypeMapper) *binder.Symbol {
	if c.getCheckFlags(symbol) &instantiated != 0 {
		var links = c.getSymbolLinks(symbol)
		// If symbol being instantiated is itself a instantiation, fetch the original target and combine the
		// type mappers. This ensures that original type identities are properly preserved and that aliases
		// always reference a non-aliases.
		symbol = links.target
		mapper = c.combineTypeMappers(links.mapper, mapper)
	}

	// Keep the flags from the symbol we're instantiating.  Mark that is instantiated, and
	// also transient so that we can just store data on it directly.
	var result = newSymbol(symbol.Flags, symbol.Name)
	var link = c.getSymbolLinks(result)
	link.checkFlags = instantiated
	if symbol.Declarations != nil {
		result.Declarations = make([]ast.Declaration, len(symbol.Declarations))
		copy(result.Declarations, symbol.Declarations)
	}
	result.Parent = symbol.Parent
	link.target = symbol
	link.mapper = mapper
	if symbol.ValueDeclaration != nil {
		result.ValueDeclaration = symbol.ValueDeclaration
	}

	return result
}

func (c *checker) instantiateAnonymousType(tpe ObjectType, mapper *TypeMapper) ObjectType {
	var result = c.newObjectType(TFAnonymous, tpe.Symbol())
	result.fields = c.instantiateSymbolList(c.getFieldsOfType(tpe), mapper, c.instantiateSymbol)
	result.methods = c.instantiateSymbolList(c.getMethodsOfType(tpe), mapper, c.instantiateSymbol)
	// !!!
	result.members = c.createSymbolTable(append(result.fields, result.methods...))
	result.callSignatures = c.instantiateSignatureList(c.getSignaturesOfType(tpe, SIKCall), mapper, c.instantiateSignature)
	result.constructSignatures = c.instantiateSignatureList(c.getSignaturesOfType(tpe, SIKConstruct), mapper, c.instantiateSignature)
	return result
}

func (c *checker) instantiateType(tpe Type, mapper *TypeMapper) Type {
	if mapper != c.identityMapper {
		if tpe.Flags()&TFTypeParameter != 0 {
			return mapper.Invoke(tpe)
		}
		if tpe.Flags()&TFInBounds != 0 {
			var inBoundsType = tpe.(*inBoundsType)
			return c.getOutBoundsType(mapper.Invoke(inBoundsType.target))
		}
		if tpe.Flags()&TFOutBounds != 0 {
			var outBoundsType = tpe.(*outBoundsType)
			return c.getOutBoundsType(mapper.Invoke(outBoundsType.target))
		}
		if tpe.Flags()&TFAnonymous != 0 {
			if tpe.Symbol() != nil && tpe.Symbol().Flags&(binder.Method|binder.Function) != 0 {
				return c.instantiateAnonymousType(tpe.(ObjectType), mapper)
			} else {
				return tpe
			}
		}
		if tpe.Flags()&TFReference != 0 {
			var typeReference = tpe.(TypeReference)
			return c.createTypeReference(typeReference.Target(), c.instantiateTypeList(typeReference.TypeArguments(), mapper, c.instantiateType))
		}
	}
	return tpe
}

func (c *checker) instantiateTypeList(items []Type, mapper *TypeMapper, instantiator func(item Type, mapper *TypeMapper) Type) []Type {
	if len(items) > 0 {
		var result []Type
		for i := 0; i < len(items); i++ {
			result = append(result, instantiator(items[i], mapper))
		}
		return result
	}
	return items
}

func (c *checker) instantiateSignatureList(items []*Signature, mapper *TypeMapper, instantiator func(item *Signature, mapper *TypeMapper, eraseTypeParameters bool) *Signature) []*Signature {
	if len(items) > 0 {
		var result []*Signature
		for i := 0; i < len(items); i++ {
			result = append(result, instantiator(items[i], mapper, false))
		}
		return result
	}
	return items
}

func (c *checker) instantiateTypeParameterList(items []*typeParameter, mapper *TypeMapper, instantiator func(item *typeParameter, mapper *TypeMapper) *typeParameter) []*typeParameter {
	if len(items) > 0 {
		var result []*typeParameter
		for i := 0; i < len(items); i++ {
			result = append(result, instantiator(items[i], mapper))
		}
		return result
	}
	return items
}

func (c *checker) instantiateSymbolList(items []*binder.Symbol, mapper *TypeMapper, instantiator func(item *binder.Symbol, mapper *TypeMapper) *binder.Symbol) []*binder.Symbol {
	if len(items) > 0 {
		var result []*binder.Symbol
		for i := 0; i < len(items); i++ {
			result = append(result, instantiator(items[i], mapper))
		}
		return result
	}
	return items
}
